package org.apache.lucene.store;

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.IOException;
import java.util.Map;

import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.UnicodeUtil;

/**
 * Abstract base class for performing write operations of Lucene's low-level
 * data types.
 */
public abstract class DataOutput {

	/**
	 * Writes a single byte.
	 * 
	 * @see IndexInput#readByte()
	 */
	public abstract void writeByte(byte b) throws IOException;

	/**
	 * Writes an array of bytes.
	 * 
	 * @param b
	 *            the bytes to write
	 * @param length
	 *            the number of bytes to write
	 * @see DataInput#readBytes(byte[],int,int)
	 */
	public void writeBytes(byte[] b, int length) throws IOException {
		writeBytes(b, 0, length);
	}

	/**
	 * Writes an array of bytes.
	 * 
	 * @param b
	 *            the bytes to write
	 * @param offset
	 *            the offset in the byte array
	 * @param length
	 *            the number of bytes to write
	 * @see DataInput#readBytes(byte[],int,int)
	 */
	public abstract void writeBytes(byte[] b, int offset, int length) throws IOException;

	/**
	 * Writes an int as four bytes.
	 * 
	 * @see DataInput#readInt()
	 */
	public void writeInt(int i) throws IOException {
		writeByte((byte) (i >> 24));
		writeByte((byte) (i >> 16));
		writeByte((byte) (i >> 8));
		writeByte((byte) i);
	}

	/**
	 * Writes an int in a variable-length format. Writes between one and five
	 * bytes. Smaller values take fewer bytes. Negative numbers are not
	 * supported.
	 * 
	 * @see DataInput#readVInt()
	 */
	public final void writeVInt(int i) throws IOException {
		while ((i & ~0x7F) != 0) {
			writeByte((byte) ((i & 0x7f) | 0x80));
			i >>>= 7;
		}
		writeByte((byte) i);
	}

	/**
	 * Writes a long as eight bytes.
	 * 
	 * @see DataInput#readLong()
	 */
	public void writeLong(long i) throws IOException {
		writeInt((int) (i >> 32));
		writeInt((int) i);
	}

	/**
	 * Writes an long in a variable-length format. Writes between one and nine
	 * bytes. Smaller values take fewer bytes. Negative numbers are not
	 * supported.
	 * 
	 * @see DataInput#readVLong()
	 */
	public final void writeVLong(long i) throws IOException {
		while ((i & ~0x7F) != 0) {
			writeByte((byte) ((i & 0x7f) | 0x80));
			i >>>= 7;
		}
		writeByte((byte) i);
	}

	/**
	 * Writes a string.
	 * 
	 * @see DataInput#readString()
	 */
	public void writeString(String s) throws IOException {
		final BytesRef utf8Result = new BytesRef(10);
		UnicodeUtil.UTF16toUTF8(s, 0, s.length(), utf8Result);
		writeVInt(utf8Result.length);
		writeBytes(utf8Result.bytes, 0, utf8Result.length);
	}

	private static int COPY_BUFFER_SIZE = 16384;
	private byte[] copyBuffer;

	/** Copy numBytes bytes from input to ourself. */
	public void copyBytes(DataInput input, long numBytes) throws IOException {
		assert numBytes >= 0 : "numBytes=" + numBytes;
		long left = numBytes;
		if (copyBuffer == null)
			copyBuffer = new byte[COPY_BUFFER_SIZE];
		while (left > 0) {
			final int toCopy;
			if (left > COPY_BUFFER_SIZE)
				toCopy = COPY_BUFFER_SIZE;
			else
				toCopy = (int) left;
			input.readBytes(copyBuffer, 0, toCopy);
			writeBytes(copyBuffer, 0, toCopy);
			left -= toCopy;
		}
	}

	/**
	 * Writes a sub sequence of characters from s as the old format (modified
	 * UTF-8 encoded bytes).
	 * 
	 * @param s
	 *            the source of the characters
	 * @param start
	 *            the first character in the sequence
	 * @param length
	 *            the number of characters in the sequence
	 * @deprecated -- please pre-convert to utf8 bytes instead or use
	 *             {@link #writeString}
	 */
	@Deprecated
	public void writeChars(String s, int start, int length) throws IOException {
		final int end = start + length;
		for (int i = start; i < end; i++) {
			final int code = s.charAt(i);
			if (code >= 0x01 && code <= 0x7F)
				writeByte((byte) code);
			else if (((code >= 0x80) && (code <= 0x7FF)) || code == 0) {
				writeByte((byte) (0xC0 | (code >> 6)));
				writeByte((byte) (0x80 | (code & 0x3F)));
			} else {
				writeByte((byte) (0xE0 | (code >>> 12)));
				writeByte((byte) (0x80 | ((code >> 6) & 0x3F)));
				writeByte((byte) (0x80 | (code & 0x3F)));
			}
		}
	}

	/**
	 * Writes a sub sequence of characters from char[] as the old format
	 * (modified UTF-8 encoded bytes).
	 * 
	 * @param s
	 *            the source of the characters
	 * @param start
	 *            the first character in the sequence
	 * @param length
	 *            the number of characters in the sequence
	 * @deprecated -- please pre-convert to utf8 bytes instead or use
	 *             {@link #writeString}
	 */
	@Deprecated
	public void writeChars(char[] s, int start, int length) throws IOException {
		final int end = start + length;
		for (int i = start; i < end; i++) {
			final int code = s[i];
			if (code >= 0x01 && code <= 0x7F)
				writeByte((byte) code);
			else if (((code >= 0x80) && (code <= 0x7FF)) || code == 0) {
				writeByte((byte) (0xC0 | (code >> 6)));
				writeByte((byte) (0x80 | (code & 0x3F)));
			} else {
				writeByte((byte) (0xE0 | (code >>> 12)));
				writeByte((byte) (0x80 | ((code >> 6) & 0x3F)));
				writeByte((byte) (0x80 | (code & 0x3F)));
			}
		}
	}

	public void writeStringStringMap(Map<String, String> map) throws IOException {
		if (map == null) {
			writeInt(0);
		} else {
			writeInt(map.size());
			for (final Map.Entry<String, String> entry : map.entrySet()) {

				String key = entry.getKey() == null ? "" : entry.getKey();
				String value = entry.getValue() == null ? "" : entry.getValue();

				System.out.println(key + "=" + value);

				writeString(key);
				writeString(value);

			}
		}
	}
}
